new MyPromise((resolve, reject) => {
  resolve("val");
})
  .then((result) => {
    return result;
  })
  .catch((err) => err);

const STATUS = {
  PENDING: "pending",
  FULFILLED: "fulfilled",
  REJECTED: "rejected",
};

const asyncRun = (function () {
  const async =
    (typeof process !== "undefined" && process.nextTick) ||
    (typeof setImmediate !== "undefined" && setImmediate) ||
    setTimeout;
  return function (callback) {
    async(callback, 0);
  };
})();

export class MyPromise {
  status = STATUS.PENDING;
  resolvedCallbacks = [];
  rejectedCallbacks = [];
  value = null;
  reason = null;
  constructor(fn) {
    try {
      fn(this.resolve, this.reject);
    } catch (error) {
      this.reject(error);
    }
  }
  resolve = (val) => {
    if (this.status !== STATUS.PENDING) return;
    this.status = STATUS.FULFILLED;
    this.value = val;
    while (this.resolvedCallbacks.length) this.resolvedCallbacks.shift(val);
  };
  reject = (err) => {
    if (this.status !== STATUS.PENDING) return;
    this.status = STATUS.REJECTED;
    this.reason = err;
    while (this.rejectedCallbacks.length) this.rejectedCallbacks.shift(err);
  };

  then(onResolve, onReject) {
    onResolve = onResolve || ((res) => res);
    onReject =
      onReject ||
      ((err) => {
        throw err;
      });
    const promise = new MyPromise((resolve, reject) => {
      if (this.status === STATUS.PENDING) {
        this.resolvedCallbacks.push((val) => {
          asyncRun(() => {
            try {
              const result = onResolve(val);
              handleResult(result, promise, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });
        this.rejectedCallbacks.push((val) => {
          asyncRun(() => {
            try {
              const result = onReject(val);
              handleResult(result, promise, resolve, reject);
            } catch (error) {
              reject(error);
            }
          });
        });
      } else if (this.status === STATUS.FULFILLED) {
        asyncRun(() => {
          try {
            const result = onResolve(this.value);
            handleResult(result, promise, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      } else if (this.status === STATUS.REJECTED) {
        asyncRun(() => {
          try {
            const result = onReject(val);
            handleResult(result, promise, resolve, reject);
          } catch (error) {
            reject(error);
          }
        });
      }
    });
    return promise;
  }

  static resolve(val) {
    if (val instanceof MyPromise || (val && typeof val.then === "function")) {
      return val;
    } else {
      return new Promise((res) => res(val));
    }
  }

  static all(fns) {
    if (!fns[Symbol.iterator]) throw new TypeError("");
    if (!Array.isArray(fns)) return [fns];
    const results = [];
    const promise = new MyPromise((resolve, reject) => {
      try {
        fns.forEach((fn, idx) => {
          if (
            fn instanceof MyPromise ||
            (fn && typeof fn.then === "function")
          ) {
            fn.then(
              (val) => handleResult(val, idx, resolve),
              (err) => reject(err)
            );
          } else {
            handleResult(fn, idx, resolve);
          }
        });
      } catch (error) {
        reject(error);
      }
    });

    function handleResult(result, idx, resolve) {
      results[idx] = result;
      if (results.length === fns.length) {
        resolve(results);
      }
    }
    return promise;
  }

  finally(fn) {
    return this.then(
      (val) => MyPromise.resolve(fn()).then(() => val),
      (reason) => {
        MyPromise.resolve(fn()).then(() => {
          throw reason;
        });
      }
    );
  }

  catch(onReject) {
    return this.then(null, onReject);
  }
}

function handleResult(result, promise, resolve, reject) {
  if (result === promise) {
    throw new Error("");
  } else if (
    result instanceof MyPromise ||
    (result && typeof result.then === "function")
  ) {
    result.then(resolve, reject);
  } else {
    resolve(result);
  }
}
